#pragma once
#include "vector.h"
#include "plane.h"
#include "polygon.h"

/**
 * Klasa symulatora fizyki.
 * Symulator przeprowadza testy kolizji sfery jednostkowej (tj o promieniu 1)
 * ze scena i oblicza odpowiedz. 
 *
 * Przyklad uzycia symulatora:
 * @code
 * Physics physics;
 * Vector spherePosition, sphereVelocity;
 * std::list<class Polygon*> levelPolygons;
 * 
 * void checkPhysics()
 * {
 * 	   physics.resetStats();
 * 
 * 	   // sprawdz kolizje z scena
 * 	   Physics::CollisionPacket col = Physics::CollisionPacket(
 * 	       spherePosition / SPHERE_RADIUS, sphereVelocity / SPHERE_RADIUS);
 *     std::list<class Polygon*>::const_iterator it;
 * 	   do
 * 	   {
 * 	       for(it = levelPolygons.begin(); it != levelPolygons.end(); it++)
 * 	           physics.checkPolygonCollision(*(*it), col);
 * 	   } while(!physics.doResponse(col));
 * 
 * 	   // ustaw nowa pozycje sfery
 *     spherePosition = col.finalPosition * SPHERE_RADIUS;
 * }
 * @endcode
 *
 * @author Krzysztof Klein
 * @version 0.1
 */
class Physics
{
public:

	/**
	 * Ustawienia symulatora
	 */
	struct Settings
	{
		/**
		 * Dokladnosc (precyzja) obliczen
		 */
		float precision;

		/**
		 * Dlugosc wzglednie mala
		 * Symulator bedzie uznawal ta dlugosc jako 0
		 */
		float veryShortLength;

		/**
		 * Sprawdzaj tylko widoczne sciany
		 */
		bool frontFacesOnly;

		/**
		 * Sprawdzaj sfery otaczajace
		 */
		bool boundingSphereCheck;

		/**
		 * Sprawdzaj prostopadlosciany otaczajace
		 */
		bool boundingBoxCheck;

		/**
		 * Liczba iteracji obliczania symulacji odpowiedzi
		 */
		int responseIterations;

	};

	/**
	 * Zwraca ustawienia symulatora
	 */
	const Settings& getSettings() const { return settings; }
	
	/**
	 * Zwraca ustawienia symulatora
	 */
	Settings& getSettings() { return settings; }

private:

	/**
	 * Ustawienia symulatora
	 */
	Settings settings;

public:

	/**
	 * Statystyki
	 */
	struct Stats
	{
		/**
		 * Pomocnicza klasa obliczajaca czas
		 */
		class Time
		{

			/**
			 * Czas poczatku
			 */
			int beginTime;

			/**
			 * Czas konca
			 */
			int endTime;

			/**
			 * Zmienna, w ktorej umieszczony zostanie czas
			 */
			int* outVar; 


			/**
			 * Konstruktor domyslny (ukryty)
			 */
			Time() {}

			/**
			 * Zwraca aktualny czas systemowy
			 *
			 * @return Aktualny czas systemowy
			 */
			static int getTimeValue();

		public:

			/**
			 * Konstruktor z zmienna, w ktorej umieszczony zostanie czas.
			 *
			 * @param outVar Adres zmiennej, w ktorej umieszczony zostanie czas
			 */
			Time(int* outVar) : endTime(0), outVar(outVar) { beginTime = getTimeValue(); }

			/**
			 * Destruktor.
			 * Ustawia wartosc czasu w docelowej zmiennej.
			 */
			~Time() { endTime = getTimeValue(); *outVar += (endTime - beginTime); }

		};

		/**
		 * Czas wszystkich symulacji (w milisekundach)
		 */
		int totalTime;

		/**
		 * Liczba przeprowadzonych symulacji
		 */
		int simulationCount;

		/**
		 * Czas ostatniej symulacji (w milisekundach)
		 */
		int lastTime;

		/**
		 * Czas sprawdzania kolizji (w milisekundach)
		 */
		int checkTime;

		/**
		 * Czas obliczenia odpowiedzi (w milisekundach)
		 */
		int responseTime;

		/**
		 * Licza sprawdzonych wielokatow
		 */
		int polygonsChecked;

		/**
		 * Liczba wielokatow odrzuconych podczas sprawdzania sfery otaczajacej
		 */
		int polysRejectedInBoundingSphereCheck;

		/**
		 * Liczba wielokatow odrzuconych podczas sprawdzania
		 * prostopadloscianu otaczajacego
		 */
		int polysRejectedInBoundingBoxCheck;

		/**
		 * Liczba wielokatow odrzuconych podczas testu widocznosci
		 */
		int polysRejectedInFrontFaceTest;

	};

	/**
	 * Zwraca statystyki symulatora
	 */
	const Stats& getStats() const { return stats; }

	/**
	 * Resetuje statystyki symulatora
	 */
	void resetStats();

private:
	
	/**
	 * Statystyki symulatora
	 */
	mutable Stats stats;

public:

	/**
	 * Pakiet danych detekcji kolizji
	 */
	struct CollisionPacket
	{

		/**
		 * Pozycja poczatkowa sfery
		 */
		Vector basePosition;


		/**
		 * Pozycja wyjsciowa sfery
		 */
		Vector finalPosition;

		/**
		 * Wektor predkosci sfery
		 */
		Vector velocity;

		/**
		 * Znormalizowany wektor predkosci sfery
		 */
		Vector normalizedVelocity;

		/**
		 * True jesli nastapila kolizja. Flase jesli nie.
		 * Flaga ta nie jest zerowana podczas obliczania odpowiedzi.
		 */
		bool hitAnything;

		/**
		 * True jesli znaleziono kolizje. False jesli nie.
		 * Flaga ta jest zerowana podczas obliczania odpowiedzi.
		 */
		bool foundCollision;

		/**
		 * Odleglosc sfery od kolizji
		 */
		float nearestDistance;

		/**
		 * Punkt kolizji
		 */
		Vector intersectionPoint;

		/**
		 * Aktualna iteracja
		 */
		int responseIteration;

		/**
		 * Konstruktor z pozycja sfery i jej predkoscia
		 *
		 * @param basePosition Pozycja sfery
		 * @param velocity Predkosc sfery
		 */
		CollisionPacket(Vector basePosition, Vector velocity)
			: basePosition(basePosition), velocity(velocity),
			normalizedVelocity(velocity), hitAnything(false),
			foundCollision(false), responseIteration(0),
			finalPosition(basePosition)
		{
			normalizedVelocity.normalize();
		}

	};

	/**
	 * Konstruktor domyslny
	 */
	Physics()
	{
		stats.totalTime = 0;
		stats.simulationCount = 0;
	}

protected:

	/**
	 * Oblicza najmniejszy dodatni pierwiastek rownania kwadratowego
	 * mniejszy od zadanej wartosci.
	 *
	 * @param a Wspolczynnik a rownania kwadratowego
	 * @param b Wspolczynnik b rownania kwadratowego
	 * @param c Wspolczynnik c rownania kwadratowego
	 * @param maxValue Maksymalna szukana wartosc pierwiastka
	 * @param outValue Zmienna do ktorej zostanie wpisana wartosc pierwiastka
	 * @return True jesli znaleziono. flase jesli nie.
	 */
	static bool getLowestPositiveRoot(float a, float b, float c, float maxValue,
		float &outValue);


	/**
	 * Sprawdza czy punkt lezy w wielokacie.
	 * Punkt musi lezec na plaszczyznie trojkata.
	 *
	 * @param poly Wielokat
	 * @param point Sprawdzany punkt
	 * @return True jesli punkt lezy w wielokacie. False jesli nie.
	 */
	static bool checkPointInPolygon(const class Polygon &poly, const Vector &point);

public:

	/**
	 * Sprawdza kolizje z wielokatem.
	 *
	 * @param poly Wielokat
	 * @param col Pakiet kolizji
	 */
	void checkPolygonCollision(const class Polygon &poly, Physics::CollisionPacket &col) const;


	/**
	 * Oblicza odpowiedz symulatora na podstawie pakietu kolizji
	 *
	 * @param col Pakiet kolizji
	 * @return True jesli koniec symulacji. False jesli nie.
	 */
	bool doResponse(Physics::CollisionPacket &col);

};
